import createMarkdown from "markdown-it"
import MarkdownItAnchor from "markdown-it-anchor"
import MarkdownItTOC, { TocAst } from "markdown-it-toc-done-right"
import MarkdownContainer from "markdown-it-container"
import Prism from "prismjs"
import { LoaderContext } from "webpack"

// 语言解析出参
interface LangConfig {
  grammar: object
  lang: string
}
// useMarkdownResolver 输入
export interface CodeHandlerParams {
  /** 高亮解析后的代码块 html */
  codeHTML: string
  /** 基于那种语言进行高亮解析 */
  highlightLang: string
  /** 高亮解析前的代码块 markdown */
  codeSource: string
  /** 解析 markdown 代码块定义的语言 */
  sourceLang: string
}
// codeHandler 类型
export interface CodeHandler {
  (params: CodeHandlerParams): string
}
// container 配置
export type ContainerOpts = MarkdownResolverOptions["containerOptions"] & {}
// container 渲染函数
export interface ContainerRender {
  (tokens: any[], idx: number, simpleMarkdownIt: { [k: string]: any }, containerOpts: ContainerOpts): string
}
// useMarkdownResolver 入参
export interface MarkdownResolverOptions {
  /** 预设模式 */
  preset?: "default" | "commonmark" | "zero"
  /** 包裹整个 html 的 div.class */
  contentClass?: string
  /** prismjs无法解析语言的映射：{ 读取的语言: 目标语言 } */
  languageMap?: { [k: string]: string }
  /** markdown-it 配置：是否在源码中启用 HTML 标签 */
  html?: boolean
  /** markdown-it 配置：是否使用 "/" 来闭合单标签 */
  xhtmlOut?: boolean
  /** markdown-it 配置：转换段落里的 '\n' 到 <br> */
  breaks?: boolean
  /** markdown-it 配置：<pre /> 代码块的 CSS 类名前缀 */
  langPrefix?: string
  /** markdown-it 配置：将类似 URL 的文本自动转换为链接 */
  linkify?: boolean
  /** markdown-it 配置：启用一些语言中立的替换 + 引号美化 */
  typographer?: boolean
  /** markdown-it 配置：双 + 单引号替换对，当 typographer 启用时 */
  quotes?: string
  /** 使用 markdown 实例作为入参，提供更好的自定义 */
  handler?: (markdownIt: any, simpleMarkdownIt: any, loaderContext?: MarkdownLoaderContent) => void
  /* 在抛给 markdown-it 之前，对高亮解析后的代码块 html 进行二次处理 */
  codeHandler?: CodeHandler
  /** 是否使用 markdown-it-anchor 插件 */
  useAnchor?: boolean
  /** markdown-it-anchor 的配置 */
  anchorOptions?: object
  /** 是否使用 markdown-it-toc-done-right 插件 */
  useTOC?: boolean
  /** markdown-it-toc-done-right 的配置 */
  TOCOptions?: { [k: string]: any }
  /** 是否使用 markdown-it-container 插件  */
  useContainer?: boolean
  /** markdown-it-container 配置 */
  containerOptions?: {
    containerClass?: string
    titleClass?: string
    bodyClass?: string
    validate?: (params: string) => string
    render?: (tokens: any[], idx: number) => string
    marker?: string
    renderMap?: { [k: string]: ContainerRender }
  }
}
// useMarkdownResolver 出参
export interface MarkdownResolver {
  /** markdownIt 实例 */
  markdownIt: any
  /** 用于处理的 markdown 文本的解析器 */
  resolver: (source: string) => { html: string; TOCCode: string }
  /** 更新 markdownIt */
  update: (opts: MarkdownResolverOptions) => void
}


export type MarkdownLoaderOptions = MarkdownResolverOptions & {
  /** prismjs 加载的代码块语言类型："all" | [langs]。*/
  languages: "all" | string[]
}

export type MarkdownLoaderContent = LoaderContext<MarkdownLoaderOptions>

/* ================================================================= */

// 解析需要高亮的语言配置
const resolveLanguage = (language: string, languageMap: object): LangConfig => {
  const lang = language.toLowerCase()

  const langList = [
    language.toLowerCase(), // 传入语言
    languageMap[lang], // 映射表中查找
    languageMap["default"] || "js" // 使用默认语言
  ]

  // 查找已设置语言
  const grammarLang = langList.find((lang) => !!Prism.languages[lang])

  return {
    grammar: Prism.languages[grammarLang],
    lang: grammarLang
  }
}
// languageMap: 当发现未加载语言时，映射为其他语言的映射表
const useResolveHighlight = (languageMap: object, codeHandler: CodeHandler): Function => {
  return (codeSource: string, language: string) => {
    const { grammar, lang } = resolveLanguage(language, languageMap)
    const codeHTML = Prism.highlight(codeSource, grammar, lang)

    // 外部传入 codeHandler 对高亮代码块进行二次处理
    return codeHandler({
      codeHTML: codeHTML,
      highlightLang: lang,
      codeSource: codeSource,
      sourceLang: language
    })
  }
}

/* ================================================================= */

// 默认 handler
const defaultHandler = (markdownIt: any, simpleMarkdownIt: any, loaderContext?: MarkdownLoaderContent) => {}
// 默认 codeHandler
const defaultCodeHandler = (params: CodeHandlerParams) => params.codeHTML
// 处理锚点链接的格式
const slugify = (s: any) => encodeURIComponent(String(s).trim().toLowerCase().replace(/\s+/g, "-"))
// 默认 container 渲染映射
const defaultContainerRender: { [k: string]: ContainerRender } = {
  default(tokens, idx, simpleMarkdownIt, opts): string {
    const { containerClass, titleClass, bodyClass } = opts

    const { info, nesting } = tokens[idx]

    const [type, ...titles] = info.trim().split(" ")

    const containerClassList = type ? [containerClass, type] : [containerClass]

    const title = simpleMarkdownIt.render(titles.join(" "))

    // open 标签 nesting === 1
    if (nesting === 1) {
      return `
        <div class="${containerClassList.join(" ")}">
          <div class="${titleClass}">${title}</div>
          <div class="${bodyClass}">
      `
    }
    // close 标签 nesting === -1
    else {
      return "</div></div>"
    }
  }
}

/* 用于生成一个 markdown 解析器函数 */
export function useMarkdownResolver(options: MarkdownResolverOptions = {}, loaderContext?: MarkdownLoaderContent): MarkdownResolver {
  const { preset = "default", contentClass = "imhjh-md-content", langPrefix = "imhjh-md-" } = options
  const { linkify, typographer, quotes, html, xhtmlOut, breaks } = options
  const { handler = defaultHandler, codeHandler = defaultCodeHandler, languageMap = {} } = options
  const { useAnchor = false, anchorOptions } = options
  const { useTOC = false, TOCOptions = {} } = options
  const { useContainer = false, containerOptions = {} } = options

  let TOCCode = ""

  const markdownIt = createMarkdown(preset, {
    html: html, // 在源码中启用 HTML 标签
    xhtmlOut: xhtmlOut, // 使用 "/" 来闭合单标签
    breaks: breaks, // 转换段落里的 '\n' 到 <br>。
    langPrefix: langPrefix, // pre>code 代码块的 CSS 语言前缀
    linkify: linkify, // 将类似 URL 的文本自动转换为链接
    typographer: typographer, // 启用一些语言中立的替换 + 引号美化
    quotes: quotes, // 双 + 单引号替换对，当 typographer 启用时
    // 高亮函数，返回转义的HTML
    highlight: useResolveHighlight(languageMap, codeHandler)
  })

  // 用于解析 container 的 title 处的 markdown
  const simpleMarkdownIt = createMarkdown(preset, {
    html: html, // 在源码中启用 HTML 标签
    xhtmlOut: xhtmlOut, // 使用 "/" 来闭合单标签
    breaks: breaks, // 转换段落里的 '\n' 到 <br>。
    langPrefix: langPrefix, // pre>code 代码块的 CSS 语言前缀
    linkify: linkify, // 将类似 URL 的文本自动转换为链接
    typographer: typographer, // 启用一些语言中立的替换 + 引号美化
    quotes: quotes, // 双 + 单引号替换对，当 typographer 启用时
    // 高亮函数，返回转义的HTML
    highlight: useResolveHighlight(languageMap, codeHandler)
  })

  // 添加锚点插件
  if (useAnchor) {
    const opts = {
      slugify,
      permalink: MarkdownItAnchor.permalink.linkInsideHeader({
        class: "imhjh-md-anchor",
        placement: "before",
        space: false,
        symbol: "#"
      }),
      ...anchorOptions
    }
    markdownIt.use(MarkdownItAnchor, opts)
  }

  // 添加目录插件
  if (useTOC) {
    const { callback, ...otherTOCOpts } = TOCOptions
    const opts = {
      slugify,
      listType: "ul",
      containerClass: "imhjh-md-toc",
      linkClass: "imhjh-md-toc-anchor",
      callback: (tocCode: string, ast: TocAst) => {
        TOCCode = tocCode

        callback && callback(tocCode, ast)
      },
      ...otherTOCOpts
    }
    markdownIt.use(MarkdownItTOC, opts)
  }

  // 添加自定义容器
  if (useContainer) {
    const opts = {
      containerClass: "imhjh-md-container",
      titleClass: "imhjh-md-container-title",
      bodyClass: "imhjh-md-container-body",
      ...containerOptions,
      renderMap: {
        ...defaultContainerRender,
        ...containerOptions.renderMap
      }
    }
    markdownIt.use(MarkdownContainer, opts.containerClass, {
      // 校验 容器名称 判断是否正确
      validate: (params: string) => {
        return true
      },
      // 容器的 token 渲染方法
      render: (tokens: any[], idx: number) => {
        const { info } = tokens[idx]
        const [type] = info.trim().split(" ")
        const { renderMap } = opts
        const render = renderMap[type] || renderMap["default"]
        return render(tokens, idx, simpleMarkdownIt, opts as ContainerOpts)
      },
      marker: ":", // 容器标识
      ...opts
    })
  }

  handler(markdownIt, simpleMarkdownIt, loaderContext)

  return {
    /* 生成的 markdown 实例 */
    markdownIt,
    /* 解析器 */
    resolver: (source) => {
      const code = markdownIt.render(source)
      const html = `<div class="${contentClass}">${code}</div>`
      return { html: html, TOCCode: TOCCode }
    },
    /* 更新配置 */
    update: (updateOpts) => {
      const { handler = defaultHandler, codeHandler = defaultCodeHandler, languageMap = {}, ...other } = updateOpts
      markdownIt.set({
        highlight: useResolveHighlight(languageMap, codeHandler),
        ...other
      })
      simpleMarkdownIt.set({
        highlight: useResolveHighlight(languageMap, codeHandler),
        ...other
      })
      handler(markdownIt, simpleMarkdownIt)
    }
  }
}
